//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Linq;
using EffiProzDB.Persist;
using EffiProzDB.Lib;

namespace EffiProzDB
{

    /**
     * Manages all SCHEMA related database objects
     *
     * @author fredt@users
     * @version  1.8.0
     * @since 1.8.0
     */
    public class SchemaManager
    {

        public const string SYSTEM_SCHEMA = "SYSTEM_SCHEMA";
        public const string DEFINITION_SCHEMA = "DEFINITION_SCHEMA";
        public const string INFORMATION_SCHEMA = "INFORMATION_SCHEMA";
        public const string PUBLIC_SCHEMA = "PUBLIC";
        public static HsqlNameManager.HsqlName INFORMATION_SCHEMA_HSQLNAME =
            HsqlNameManager.newHsqlSystemObjectName(INFORMATION_SCHEMA);
        public static HsqlNameManager.HsqlName SYSTEM_SCHEMA_HSQLNAME =
            HsqlNameManager.newHsqlSystemObjectName(SYSTEM_SCHEMA);
        public Database database;
        public HsqlNameManager.HsqlName defaultSchemaHsqlName;
        public HashMappedList<string, Schema> schemaMap = new HashMappedList<string, Schema>();

        public SchemaManager(Database database)
        {

            this.database = database;

            Schema schema = new Schema(this, PUBLIC_SCHEMA, false);

            defaultSchemaHsqlName = schema.name;

            schemaMap.Add(PUBLIC_SCHEMA, schema);
        }

        public void createSchema(string name, bool isQuoted)
        {

            if (DEFINITION_SCHEMA.Equals(name) || INFORMATION_SCHEMA.Equals(name)
                    || SYSTEM_SCHEMA.Equals(name))
            {
                throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
            }

            Schema schema = new Schema(this,name, isQuoted);

            schemaMap.Add(name, schema);
        }

        public void dropSchema(string name, bool cascade)
        {
            Schema schema = null; 

            if (!schemaMap.TryGetValue(name,out schema))
            {
                throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
            }

            if (!cascade && !schema.isEmpty())
            {
                throw Trace.error(Trace.DEPENDENT_DATABASE_OBJECT_EXISTS);
            }

            IEnumerable<Table> tableIterator = schema.tablesIterator();

            foreach (var table in tableIterator)
            { 
                database.getUserManager().removeDbObject(table.getName());
                table.drop();
            }

            IEnumerable<NumberSequence> sequenceIterator = schema.sequencesIterator();

            foreach (var sequence in sequenceIterator)
            {          
                database.getUserManager().removeDbObject(sequence.getName());
            }

            schema.clearStructures();
            schemaMap.Remove(name);

            if (defaultSchemaHsqlName.name.Equals(name))
            {
                if (schemaMap.Count == 0)
                {
                    schema = new Schema(this,PUBLIC_SCHEMA, false);
                }
                else
                {
                    schema = schemaMap.Values[0];
                }

                defaultSchemaHsqlName = schema.name;

                schemaMap.Add(defaultSchemaHsqlName.name, schema);
            }

            // these are called last and _in this particular order
            database.getUserManager().removeSchemaReference(schema);
            database.getSessionManager().removeSchemaReference(schema);
        }

        public void renameSchema(string name, string newName,
                          bool isQuoted)
        {

            Schema schema = null;
            Schema exists = null; 

            schemaMap.TryGetValue(name, out schema);
            schemaMap.TryGetValue(newName,out exists);

            if (schema == null || exists != null
                    || INFORMATION_SCHEMA.Equals(newName))
            {
                throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS,
                                  schema == null ? name
                                                 : newName);
            }

            schema.name.rename(newName, isQuoted);     
            schemaMap.Add( newName, schema);
        }

        public void clearStructures()
        {          
            foreach (var schema in schemaMap.Values)
            {              

                schema.clearStructures();
            }
        }

        public IEnumerable<string> userSchemaNameIterator()
        {
            return schemaMap.Keys;
        }

        public HsqlNameManager.HsqlName toSchemaHsqlName(string name)
        {

            Schema schema;

            return !schemaMap.TryGetValue(name,out schema) ? null
                                  : schema.name;
        }

        public HsqlNameManager.HsqlName getDefaultSchemaHsqlName()
        {
            return defaultSchemaHsqlName;
        }

        public string getDefaultSchemaName()
        {
            return defaultSchemaHsqlName.name;
        }

        public bool schemaExists(string name)
        {

            if (INFORMATION_SCHEMA.Equals(name))
            {
                return true;
            }

            return schemaMap.ContainsKey(name);
        }

        /**
         * If schemaName is null, return the current schema name, else return
         * the HsqlNameManager.HsqlName object for the schema. If schemaName does not exist,
         * throw.
         */
        public HsqlNameManager.HsqlName getSchemaHsqlName(string name)
        {

            if (name == null)
            {
                return defaultSchemaHsqlName;
            }

            if (INFORMATION_SCHEMA.Equals(name))
            {
                return INFORMATION_SCHEMA_HSQLNAME;
            }

            Schema schema = null;

            if (!schemaMap.TryGetValue(name,out schema))
            {
                throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS, name);
            }

            return schema.name;
        }

        /**
         * Same as above, but return string
         */
       public string getSchemaName(string name)
        {
            return getSchemaHsqlName(name).name;
        }

        /**
         * Iterator includes INFORMATION_SCHEMA
         */
       public IEnumerable<string> fullSchemaNamesIterator()
        {            
            List<string> tmp = new List<string>();
            tmp.Add(INFORMATION_SCHEMA);

            return schemaMap.Keys.Union(tmp);
        }

        /**
         * is a schema read-only
         */
        public bool isSystemSchema(HsqlNameManager.HsqlName schema)
        {

            return (INFORMATION_SCHEMA_HSQLNAME.Equals(schema) || SYSTEM_SCHEMA_HSQLNAME.Equals(schema))
                   ? true
                   : false;
        }

        public IEnumerable<Table> tablesIterator(string schema)
        {

            Schema temp = (Schema)schemaMap[schema];

            return temp.tablesIterator();
        }

        public IEnumerable<Table> allTablesIterator()
        {

            IEnumerable<string> schemas = userSchemaNameIterator();
            IEnumerable<Table> tables= null;

            foreach (var name in schemas)
            {
          
                IEnumerable<Table> t = tablesIterator(name);
                tables = tables == null ? t :  tables.Union(t);
            }

            return tables;
        }

        public IEnumerable<NumberSequence> sequenceIterator(string schema)
        {

            Schema temp = schemaMap[schema];

            return temp.sequencesIterator();
        }

        public IEnumerable<NumberSequence> allSequencesIterator()
        {

            IEnumerable<Schema> it = schemaMap.Values;
            IEnumerable<NumberSequence> sequences =null;

            foreach (var temp in it)
            {
                if (sequences == null)
                    sequences = temp.sequencesIterator();
                else
                    sequences = temp.sequencesIterator().Union(sequences);
            }

            return sequences;
        }

        /**
         *  Returns an HsqlArrayList containing references to all non-system
         *  tables and views. This includes all tables and views registered with
         *  this Database.
         */
        public IList<Table> getAllTables()
        {

            IEnumerable<string> schemas = userSchemaNameIterator();
            List<Table> alltables = new List< Table>();

            foreach (var name in schemas)
            {
                IList<Table> current = getTables(name);
                foreach (var tbl in current)
                {
                    alltables.Add( tbl);
                }
            }

            return alltables;
        }

        public IList<Table> getTables(string schema)
        {

            Schema temp = (Schema)schemaMap[schema];

            return temp.tableList.Values;
        }

        /**
         * @  if exists.
         */
        public void checkUserViewNotExists(Session session, string viewName,
                                    string schema)
        {

            bool exists =
                database.schemaManager.findUserTable(session, viewName, schema)
                != null;

            if (exists)
            {
                throw Trace.error(Trace.VIEW_ALREADY_EXISTS, viewName);
            }
        }

        /**
         * @  if exists
         */
        public void checkUserTableNotExists(Session session, string tableName,
                                     string schema)
        {

            bool exists = findUserTable(session, tableName, schema) != null;

            if (exists)
            {
                throw Trace.error(Trace.TABLE_ALREADY_EXISTS, tableName);
            }
        }

        /**
         *  Returns the specified user-defined table or view visible within the
         *  context of the specified Session, or any system table of the given
         *  name. It excludes any temp tables created _in other Sessions.
         *  Throws if the table does not exist _in the context.
         */
        public Table getTable(Session session, string name,
                              string schema)
        {

            Table t = findUserTable(session, name, schema);

            if (t == null)
            {
                if (!INFORMATION_SCHEMA.Equals(schema))
                {
                    throw Trace.error(Trace.TABLE_NOT_FOUND);
                }

                if (database.dbInfo != null)
                {
                    t = database.dbInfo.getSystemTable(session, name);
                }
            }

            if (t == null)
            {
                throw Trace.error(Trace.TABLE_NOT_FOUND, name);
            }

            return t;
        }

        /**
         *  Returns the specified user-defined table or view visible within the
         *  context of the specified Session. It excludes system tables and
         *  any temp tables created _in different Sessions.
         *  Throws if the table does not exist _in the context.
         */
        public Table getUserTable(Session session, string name,
                                  string schema)
        {

            Table t = findUserTable(session, name, schema);

            if (t == null)
            {
                throw Trace.error(Trace.TABLE_NOT_FOUND, name);
            }

            return t;
        }

        /**
         *  Returns the specified user-defined table or view visible within the
         *  context of the specified schema. It excludes system tables.
         *  Returns null if the table does not exist _in the context.
         */
        public Table findUserTable(Session session, string name, string schemaName)
        {

            Schema schema = null;


            if (!schemaMap.TryGetValue(schemaName, out schema))
            {
                return null;
            }

            foreach (var t in schema.tableList.Values)
            {               

                if (t.Equals(session, name))
                {
                    return t;
                }
            }

            return null;
        }

        /**
         *  Registers the specified table or view with this Database.
         */
        public void linkTable(Table t)
        {

            Schema schema = (Schema)schemaMap[t.getSchemaName()];

            schema.tableList.Add(t.getName().name, t);
        }

       public NumberSequence getSequence(string name,
                                   string schemaName)
        {

            NumberSequence sequence = findSequence(name, schemaName);

            if (sequence == null)
            {
                throw Trace.error(Trace.SEQUENCE_NOT_FOUND, name);
            }

            return sequence;
        }

        /**
         *  Returns the specified Sequence visible within the
         *  context of the specified Session.
         */
        public NumberSequence findSequence(string name,
                                           string schemaName)
        {

            Schema schema = (Schema)schemaMap[schemaName];
            NumberSequence sequence = schema.sequenceManager.getSequence(name);

            return sequence;
        }

        /**
         * Returns the table that has an index with the given name and schema.
         */
        public Table findUserTableForIndex(Session session, string name,
                                    string schemaName)
        {

            Schema schema = (Schema)schemaMap[schemaName];
            HsqlNameManager.HsqlName tablename = schema.indexNameList.getOwner(name);

            if (tablename == null)
            {
                return null;
            }

            return findUserTable(session, tablename.name, schemaName);
        }

        /**
         *  Returns index of a table or view _in the HsqlArrayList that
         *  contains the table objects for this Database.
         *
         * @param  table the Table object
         * @return  the index of the specified table or view, or -1 if not found
         */
        public int getTableIndex(Table table)
        {

            Schema schema = (Schema)schemaMap[table.getSchemaName()];

            int i = 0;
            foreach (var t in schema.tableList.Values)
            {    
                
                if (t == table)
                {
                    return i;
                }
                i++;
            }

            return -1;
        }

        /**
         * Drops the index with the specified name.
         */
        public void dropIndex(Session session, string indexname, string schema,
                       bool ifExists)
        {

            Table t = findUserTableForIndex(session, indexname, schema);

            if (t == null)
            {
                if (ifExists)
                {
                    return;
                }
                else
                {
                    throw Trace.error(Trace.INDEX_NOT_FOUND, indexname);
                }
            }

            t.checkDropIndex(indexname, null, false);
            session.commit();
            session.setScripting(true);

            TableWorks tw = new TableWorks(session, t);

            tw.dropIndex(indexname);
        }

        //------------ name management

        /**
         * Checks if a Trigger with given name either exists or does not, based on
         * the value of the argument, yes.
         */
        public void checkTriggerExists(string name, string schemaName,
                                bool yes)
        {

            Schema schema = (Schema)schemaMap[schemaName];
            bool exists = schema.triggerNameList.containsName(name);

            if (exists != yes)
            {
                int code = yes ? Trace.TRIGGER_NOT_FOUND
                               : Trace.TRIGGER_ALREADY_EXISTS;

                throw Trace.error(code, name);
            }
        }

        public void registerTriggerName(string name,
                                 HsqlNameManager.HsqlName tableName)
        {

            Schema schema = schemaMap[tableName.schema.name];

            schema.triggerNameList.addName(name, tableName,
                                           Trace.TRIGGER_ALREADY_EXISTS);
        }

        public void checkIndexExists(string name, string schemaName,
                              bool yes)
        {

            Schema schema = (Schema)schemaMap[schemaName];
            bool exists = schema.indexNameList.containsName(name);

            if (exists != yes)
            {
                int code = yes ? Trace.INDEX_NOT_FOUND
                               : Trace.INDEX_ALREADY_EXISTS;

                throw Trace.error(code, name);
            }
        }

        public void registerIndexName(string name,
                               HsqlNameManager.HsqlName tableName)
        {

            Schema schema = schemaMap[tableName.schema.name];

            schema.indexNameList.addName(name, tableName,
                                         Trace.INDEX_ALREADY_EXISTS);
        }

        public void removeIndexName(string name,
                             HsqlNameManager.HsqlName tableName)
        {

            Schema schema = schemaMap[tableName.schema.name];

            schema.indexNameList.removeName(name);
        }

        public void removeIndexNames(HsqlNameManager.HsqlName tableName)
        {

            Schema schema = schemaMap[tableName.schema.name];

            schema.indexNameList.removeOwner(tableName);
        }

        public void renameIndex(string oldName, string newName,
                         HsqlNameManager.HsqlName tableName)
        {

            Schema schema = schemaMap[tableName.schema.name];

            schema.indexNameList.rename(oldName, newName,
                                        Trace.INDEX_ALREADY_EXISTS);
        }

        public void checkConstraintExists(string name, string schemaName,
                                   bool yes)
        {

            Schema schema = (Schema)schemaMap[schemaName];
            bool exists = schema.constraintNameList.containsName(name);

            if (exists != yes)
            {
                int code = yes ? Trace.CONSTRAINT_NOT_FOUND
                               : Trace.CONSTRAINT_ALREADY_EXISTS;

                throw Trace.error(code, name);
            }
        }

        public void registerConstraintName(string name,
                                    HsqlNameManager.HsqlName tableName)
        {

            Schema schema = schemaMap[tableName.schema.name];

            schema.constraintNameList.addName(name, tableName,
                                              Trace.CONSTRAINT_ALREADY_EXISTS);
        }

        public void removeConstraintName(string name,
                                  HsqlNameManager.HsqlName tableName)
        {

            Schema schema = schemaMap[tableName.schema.name];

            schema.constraintNameList.removeName(name);
        }

        public void removeConstraintNames(HsqlNameManager.HsqlName tableName)
        {

            Schema schema = schemaMap[tableName.schema.name];

            schema.constraintNameList.removeOwner(tableName);
        }

        // sequence
        public NumberSequence createSequence(HsqlNameManager.HsqlName hsqlname, long start,
                                      long increment,
                                      int type)
        {

            Schema schema = (Schema)schemaMap[hsqlname.schema.name];

            return schema.sequenceManager.createSequence(hsqlname, start,
                    increment, type);
        }

        public void dropSequence(NumberSequence sequence)
        {

            Schema schema = (Schema)schemaMap[sequence.getSchemaName()];

            schema.sequenceManager.dropSequence(sequence.getName().name);
        }

        public void logSequences(Session session, Logger logger)
        {

            foreach (var schema in schemaMap.Values)
            {            

                schema.sequenceManager.logSequences(session, logger);
            }
        }

        /**
         * Clear copies of a temporary table from all sessions apart from one.
         */
        public void clearTempTables(Session exclude, Table table)
        {

            Session[] sessions = database.sessionManager.getAllSessions();
            Index[] indexes = table.getIndexes();

            for (int i = 0; i < sessions.Length; i++)
            {
                if (sessions[i] != exclude)
                {
                    for (int j = 0; j < indexes.Length; j++)
                    {
                        sessions[i].dropIndex(indexes[j].getName(), false);
                    }
                }
            }
        }

        /**
         *  Drops the specified user-defined view or table from this Database
         *  object. <p>
         *
         *  The process of dropping a table or view includes:
         *  <OL>
         *    <LI> checking that the specified Session's currently connected User
         *    has the right to perform this operation and refusing to proceed if
         *    not by throwing.
         *    <LI> checking for referential constraints that conflict with this
         *    operation and refusing to proceed if they exist by throwing.</LI>
         *
         *    <LI> removing the specified Table from this Database object.
         *    <LI> removing any exported foreign keys Constraint objects held by
         *    any tables referenced by the table to be dropped. This is especially
         *    important so that the dropped Table ceases to be referenced,
         *    eventually allowing its full garbage collection.
         *    <LI>
         *  </OL>
         *  <p>
         *
         * @param  name of the table or view to drop
         * @param  ifExists if true and if the Table to drop does not exist, fail
         *      silently, else throw
         * @param  isView true if the name argument refers to a View
         * @param  session the connected context _in which to perform this
         *      operation
         * @throws  HsqlException if any of the checks listed above fail
         */
        public void dropTable(Session session, string name, string schemaName,
                       bool ifExists, bool isView,
                       bool cascade)
        {

            Table dripTable = null;
            int dropIndex = -1;
            Schema schema = (Schema)schemaMap[schemaName];

            int i = 0;
            foreach (var table in schema.tableList.Values)
            {
             
                if (table.Equals(session, name) && isView == table.isView())
                {
                    dropIndex = i;
                    dripTable = table;
                    break;
                }
                else
                {
                    dripTable = null;
                }

                i++;
            }

            if (dropIndex == -1)
            {
                if (ifExists)
                {
                    return;
                }
                else
                {
                    throw Trace.error(isView ? Trace.VIEW_NOT_FOUND
                                             : Trace.TABLE_NOT_FOUND, name);
                }
            }

            session.checkAdmin();
            session.checkDDLWrite();

            // ft - concurrent
            session.commit();
            dropTable(dripTable, cascade);
            session.setScripting(true);
        }

        public void dropTable(Table table, bool cascade)
        {

            Schema schema = (Schema)schemaMap[table.getSchemaName()];
            //int dropIndex = schema.tableList.getIndex(table.getName().name);

            if (table.isView())
            {
                checkCascadeDropViews((View)table, cascade);
            }
            else
            {
                checkCascadeDropReferenced(table, cascade);
                checkCascadeDropViews(table, cascade);
            }

            // get it again as table object might be a different one
            table = schema.tableList[table.getName().name];
            schema.tableList.Remove(table.getName().name);

            removeExportedKeys(table);
            database.getUserManager().removeDbObject(table.getName());
            schema.triggerNameList.removeOwner(table.tableName);
            schema.indexNameList.removeOwner(table.tableName);
            schema.constraintNameList.removeOwner(table.tableName);
            table.dropTriggers();
            table.drop();
        }

        public void setTable(int index, Table table)
        {

            Schema schema = (Schema)schemaMap[table.getSchemaName()];

            schema.tableList.Values[index] = table;
        }

        public void renameTable(Session session, Table table, string newName,
                         bool isQuoted)
        {

            Schema schema = (Schema)schemaMap[table.tableName.schema.name];
            string oldname = table.tableName.name;

            checkCascadeDropViews(table, false);
            table.rename(session, newName, isQuoted);

            schema.tableList.Remove(oldname);
            schema.tableList.Add( newName, table);
        }

        /**
         * Throws if the table is referenced _in a foreign key constraint.
         */
        private void checkCascadeDropReferenced(Table table,
                bool cascade)
        {

            Constraint[] constraints = table.getConstraints();
            Constraint currentConstraint = null;
            Table refTable = null;
            bool isSelfRef = false;

            for (int i = constraints.Length - 1; i >= 0; i--)
            {
                currentConstraint = constraints[i];

                if (currentConstraint.getType() != Constraint.MAIN)
                {
                    continue;
                }

                refTable = currentConstraint.getRef();
                isSelfRef = (refTable != null && table.Equals(refTable));

                if (isSelfRef)
                {
                    continue;
                }

                if (cascade)
                {
                    Constraint refConst =
                        refTable.getConstraint(currentConstraint.getFkName());
                    TableWorks tw = new TableWorks(null, refTable);

                    tw.dropFKConstraint(refConst);

                    constraints = table.constraintList;
                    i = constraints.Length;
                }
                else
                {
                    throw Trace.error(Trace.TABLE_REFERENCED_CONSTRAINT,
                                      Trace.Database_dropTable, new Object[] {
                    currentConstraint.getName().name, refTable.getName().name
                });
                }
            }
        }

        /**
         * Throws if the view is referenced _in a view.
         */
        public void checkCascadeDropViews(View view,
                                   bool cascade)
        {

            View[] views = getViewsWithView(view);

            if (views != null)
            {
                if (cascade)
                {

                    // drop from end to avoid repeat drop
                    for (int i = views.Length - 1; i >= 0; i--)
                    {
                        dropTable(views[i], cascade);
                    }
                }
                else
                {
                    throw Trace.error(Trace.TABLE_REFERENCED_VIEW,
                                      views[0].getName().name);
                }
            }
        }

        /**
         * Throws if the table is referenced _in a view.
         */
        public void checkCascadeDropViews(Table table,
                                   bool cascade)
        {

            View[] views = getViewsWithTable(table, null);

            if (views != null)
            {
                if (cascade)
                {

                    // drop from end to avoid repeat drop
                    for (int i = views.Length - 1; i >= 0; i--)
                    {
                        dropTable(views[i], cascade);
                    }
                }
                else
                {
                    throw Trace.error(Trace.TABLE_REFERENCED_VIEW,
                                      views[0].getName().name);
                }
            }
        }

        /**
         * Throws if the sequence is referenced _in a view.
         */
        public void checkCascadeDropViews(NumberSequence sequence,
                                   bool cascade)
        {

            View[] views = getViewsWithSequence(sequence);

            if (views != null)
            {
                if (cascade)
                {

                    // drop from end to avoid repeat drop
                    for (int i = views.Length - 1; i >= 0; i--)
                    {
                        dropTable(views[i], cascade);
                    }
                }
                else
                {
                    throw Trace.error(Trace.SEQUENCE_REFERENCED_BY_VIEW,
                                      views[0].getName().name);
                }
            }
        }

        /**
         * Throws if the column is referenced _in a view.
         */
        public void checkColumnIsInView(Table table,
                                 string column)
        {

            View[] views = getViewsWithTable(table, column);

            if (views != null)
            {
                throw Trace.error(Trace.COLUMN_IS_REFERENCED,
                                  views[0].getName().name);
            }
        }

        /**
         * Returns an array of views that reference another view.
         */
        private View[] getViewsWithView(View view)
        {

            List<Table> list = null;
            Schema schema = (Schema)schemaMap[view.getSchemaName()];

            foreach (var t in schema.tableList.Values)
            {             

                if (t.isView())
                {
                    bool found = ((View)t).hasView(view);

                    if (found)
                    {
                        if (list == null)
                        {
                            list = new List<Table>();
                        }

                        list.Add(t);
                    }
                }
            }

            return list == null ? null
                                : (View[])list.ToArray();
        }

        /**
         * Returns an array of views that reference the specified table or
         * the specified column if column parameter is not null.
         */
        private View[] getViewsWithTable(Table table, string column)
        {

            List<View> list = null;
            IEnumerable<Table> it = allTablesIterator();

            foreach (var t in it)
            {               

                if (t.isView())
                {
                    bool found = column == null ? ((View)t).hasTable(table)
                                                   : ((View)t).hasColumn(table,
                                                       column);

                    if (found)
                    {
                        if (list == null)
                        {
                            list = new List<View>();
                        }

                        list.Add((View)t);
                    }
                }
            }

            return list == null ? null
                                : list.ToArray();
        }

        /**
         * Returns an array of views that reference a sequence.
         */
        public View[] getViewsWithSequence(NumberSequence sequence)
        {

            List<Table> list = null;
            IEnumerable<Table> it = allTablesIterator();

            foreach (var t in it)
            {           

                if (t.isView())
                {
                    bool found = ((View)t).hasSequence(sequence);

                    if (found)
                    {
                        if (list == null)
                        {
                            list = new List<Table>();
                        }

                        list.Add(t);
                    }
                }
            }

            return list == null ? null
                                : (View[])list.ToArray();
        }

        /**
         * After addition or removal of columns and indexes all views that
         * reference the table should be recompiled.
         */
        public void recompileViews(Table table)
        {

            View[] viewlist = getViewsWithTable(table, null);

            if (viewlist != null)
            {
                for (int i = 0; i < viewlist.Length; i++)
                {
                    string schema = viewlist[i].compileTimeSchema.name;

                    if (!schemaExists(schema))
                    {
                        schema = null;
                    }

                    Session session =
                        database.sessionManager.getSysSession(schema, false);

                    viewlist[i].compile(session);
                }
            }
        }

        /**
         *  Removes any foreign key Constraint objects (exported keys) held by any
         *  tables referenced by the specified table. <p>
         *
         *  This method is called as the last step of a successful call to
         *  dropTable() _in order to ensure that the dropped Table ceases to be
         *  referenced when enforcing referential integrity.
         *
         * @param  toDrop The table to which other tables may be holding keys.
         *      This is a table that is _in the process of being dropped.
         */
        public void removeExportedKeys(Table toDrop)
        {

            Schema schema = (Schema)schemaMap[toDrop.getSchemaName()];

            foreach (var table in schema.tableList.Values) 
            {             

                for (int j = table.constraintList.Length - 1; j >= 0; j--)
                {
                    Table refTable = table.constraintList[j].getRef();

                    if (toDrop == refTable)
                    {
                        Array.Resize<Constraint>(ref table.constraintList, table.constraintList.Length -1);
                           
                    }
                }
            }
        }

        /**
         *  Drops a trigger with the specified name _in the given context.
         */
        public void dropTrigger(Session session, string name,
                         string schemaName)
        {

            Schema schema = (Schema)schemaMap[schemaName];
            bool found = schema.triggerNameList.containsName(name);

            Trace.check(found, Trace.TRIGGER_NOT_FOUND, name);

            HsqlNameManager.HsqlName tableName =
                (HsqlNameManager.HsqlName)schema.triggerNameList.removeName(name);
            Table t = this.findUserTable(session, tableName.name, schemaName);

            t.dropTrigger(name);
            session.setScripting(true);
        }

        public class Schema
        {

            public HsqlNameManager.HsqlName name;
            public DatabaseObjectNames triggerNameList;
            public DatabaseObjectNames constraintNameList;
            public DatabaseObjectNames indexNameList;
            public SequenceManager sequenceManager;
            private SchemaManager _o;
            public HashMappedList<string,Table> tableList;

            public Schema(SchemaManager _o,string name, bool isquoted)
            {

                this.name = _o.database.nameManager.newHsqlName(name, isquoted);
                triggerNameList = new DatabaseObjectNames();
                indexNameList = new DatabaseObjectNames();
                constraintNameList = new DatabaseObjectNames();
                sequenceManager = new SequenceManager();
                tableList = new HashMappedList<string, Table>();
                this._o = _o;
            }

            public bool isEmpty()
            {
                return sequenceManager.sequenceMap.Count == 0
                       && tableList.Count == 0;
            }

            public IEnumerable<Table> tablesIterator()
            {
                return tableList.Values;
            }

            public IEnumerable<NumberSequence> sequencesIterator()
            {
                return sequenceManager.sequenceMap.Values;
            }

            public void clearStructures()
            {

                if (tableList != null)
                {
                    foreach (var table in tableList.Values)
                    {                     
                        table.dropTriggers();
                    }
                }

                triggerNameList = null;
                indexNameList = null;
                constraintNameList = null;
                sequenceManager = null;
                tableList = null;
            }
        }
    }
}
